/* * Copyright 2012-2013 iDA MediaFoundry (www.ida-mediafoundry.be) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package be.idamediafoundry.sofa.livecycle.dsc.util; import be.idamediafoundry.sofa.livecycle.dsc.annotations.ConfigParam; import be.idamediafoundry.sofa.livecycle.dsc.annotations.FactoryMethod; import be.idamediafoundry.sofa.livecycle.dsc.annotations.Operation; import be.idamediafoundry.sofa.livecycle.dsc.annotations.Version; import be.idamediafoundry.sofa.livecycle.maven.component.configuration.Component; import be.idamediafoundry.sofa.livecycle.maven.component.configuration.ConfigParameterType; import be.idamediafoundry.sofa.livecycle.maven.component.configuration.FaultType; import be.idamediafoundry.sofa.livecycle.maven.component.configuration.InputParameterType; import be.idamediafoundry.sofa.livecycle.maven.component.configuration.OperationType; import be.idamediafoundry.sofa.livecycle.maven.component.configuration.OutputParameterType; import be.idamediafoundry.sofa.livecycle.maven.component.configuration.Service; import be.idamediafoundry.sofa.livecycle.maven.component.configuration.Service.AutoDeploy; import com.thoughtworks.qdox.model.AbstractJavaEntity; import com.thoughtworks.qdox.model.Annotation; import com.thoughtworks.qdox.model.DocletTag; import com.thoughtworks.qdox.model.JavaClass; import com.thoughtworks.qdox.model.JavaMethod; import com.thoughtworks.qdox.model.JavaParameter; import com.thoughtworks.qdox.model.Type; import com.thoughtworks.qdox.model.annotation.AnnotationConstant; import org.apache.commons.lang.StringUtils; import org.apache.maven.plugin.logging.Log; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.List; import java.util.Map; public class AnnotationDrivenQDoxComponentInfoExtractor extends AbstractQDoxComponentInfoExtractor { public AnnotationDrivenQDoxComponentInfoExtractor(String sourcePath, Log log) { super(sourcePath, log); } public void populateComponent(Component component) { //TODO we might look for bootstrap and livecycle classes... } public boolean populateServices(Service service, JavaClass javaClass) { service.setName(javaClass.getName()); service.setImplementationClass(javaClass.getFullyQualifiedName()); be.idamediafoundry.sofa.livecycle.dsc.annotations.Service serviceAnnotation = findAnnotation( javaClass, be.idamediafoundry.sofa.livecycle.dsc.annotations.Service.class); if (StringUtils.isNotBlank(serviceAnnotation.smallIcon())) { service.setSmallIcon(serviceAnnotation.smallIcon()); } if (StringUtils.isNotBlank(serviceAnnotation.largeIcon())) { service.setLargeIcon(serviceAnnotation.largeIcon()); } String comment = javaClass.getComment(); service.setHint(getFirstSentence(comment)); service.setDescription(comment); // Find factory method. JavaMethod factoryMethod = findAnnotationOnMethods(javaClass, FactoryMethod.class); if (factoryMethod != null) { if (factoryMethod.isAbstract() || factoryMethod.isConstructor() || !factoryMethod.isPublic() || factoryMethod.isPropertyAccessor() || factoryMethod.isPropertyMutator() || !factoryMethod.isStatic()) { throw new IllegalStateException( "You should not annotate " + factoryMethod.getName() + " as FactoryMethod, it is not a valid factory method!"); } service.setFactoryMethod(factoryMethod.getName()); } if (serviceAnnotation.requestProcessingStrategy() != be.idamediafoundry.sofa.livecycle.dsc.annotations.Service.RequestProcessingStrategy.NONE) { service.setRequestProcessingStrategy(serviceAnnotation.requestProcessingStrategy().name()); } return true; } public boolean populateAutoDeploy(Component component, AutoDeploy autoDeploy, JavaClass javaClass) { be.idamediafoundry.sofa.livecycle.dsc.annotations.Service serviceAnnotation = findAnnotation( javaClass, be.idamediafoundry.sofa.livecycle.dsc.annotations.Service.class); if (serviceAnnotation.autoDeploy()) { autoDeploy.setServiceId(javaClass.getName()); autoDeploy.setCategoryId(StringUtils.isBlank(serviceAnnotation.categoryId()) ? component.getComponentId() : serviceAnnotation.categoryId()); Version versionAnnotation = serviceAnnotation.version(); if (versionAnnotation != null) { if (versionAnnotation.major() > -1) { autoDeploy.setMajorVersion(versionAnnotation.major()); } if (versionAnnotation.minor() > -1) { autoDeploy.setMinorVersion(versionAnnotation.minor()); } } return true; } else { return false; } } public boolean populateOperation(OperationType operation, JavaMethod javaMethod, List<String> existinOperationNames) { Operation operationAnnotation = findAnnotation(javaMethod, Operation.class); String suggestedName = operationAnnotation == null ? null : operationAnnotation.name(); if (StringUtils.isBlank(suggestedName)) { suggestedName = null; } generateOperationNameMethodTitle(existinOperationNames, javaMethod, operation, suggestedName); String comment = javaMethod.getComment(); operation.setHint(getFirstSentence(comment)); operation.setDescription(comment); return true; } public boolean populateConfigParameter(ConfigParameterType configParameter, JavaMethod javaMethod) { String comment = javaMethod.getComment(); String propertyName = javaMethod.getPropertyName(); if (propertyName.length() > 100) { // Following spec: name must be no larger then 100 characters configParameter.setProperty(propertyName); propertyName = propertyName.substring(0, 100); } configParameter.setName(propertyName); configParameter.setType(getFullyQualifiedJavaType(javaMethod .getPropertyType())); configParameter.setHint(getFirstSentence(comment)); configParameter.setDescription(comment); configParameter.setTitle(generateTitle(propertyName)); ConfigParam configParam = findAnnotation(javaMethod, ConfigParam.class); if (configParam != null) { configParameter.setRequired(configParam.required()); if (StringUtils.isNotBlank(configParam.defaultValue())) { configParameter.setDefaultValue(configParam.defaultValue()); } } return true; } public boolean populateInputParameter(InputParameterType inputParameter, JavaMethod javaMethod, JavaParameter javaParameter) { Map<String, String> paramTagMap = getCommentMapForTag(javaMethod, PARAM_TAG); inputParameter.setName(javaParameter.getName()); inputParameter.setType(getFullyQualifiedJavaType(javaParameter .getType())); String comment = paramTagMap.get(javaParameter.getName()); inputParameter.setHint(getFirstSentence(comment)); inputParameter.setDescription(comment); inputParameter.setTitle(generateTitle(javaParameter.getName())); return true; } public boolean populateOutputParameter(OutputParameterType outputParameter, JavaMethod javaMethod) { boolean result = false; Type methodResultType = javaMethod.getReturnType(); if (!methodResultType.equals(Type.VOID)) { String outputParameterName = DEFAULT_OUT_PARAM_NAME; Operation operationAnnotation = findAnnotation(javaMethod, Operation.class); if (operationAnnotation != null) { if (StringUtils.isNotBlank(operationAnnotation.outputName())) { outputParameterName = operationAnnotation.outputName(); } } outputParameter.setName(outputParameterName); outputParameter.setTitle(outputParameterName); outputParameter .setType(getFullyQualifiedJavaType(methodResultType)); DocletTag returnDocletTag = javaMethod.getTagByName(RETURN_TAG); if (returnDocletTag != null) { String comment = returnDocletTag.getValue(); outputParameter.setHint(getFirstSentence(comment)); outputParameter.setDescription(comment); } result = true; } return result; } public boolean populateFault(FaultType fault, JavaMethod javaMethod, Type exceptionType) { Map<String, String> exceptionTagMap = getCommentMapForTag(javaMethod, "throws"); String name = exceptionType.getJavaClass().getName(); fault.setName(name); fault.setType(exceptionType.getFullyQualifiedName()); fault.setTitle(generateTitle(name)); String comment = exceptionTagMap.get(name); fault.setHint(getFirstSentence(comment)); fault.setDescription(comment); return true; } @Override public boolean acceptAsService(JavaClass javaClass) { boolean accept = false; be.idamediafoundry.sofa.livecycle.dsc.annotations.Service serviceAnnotation = findAnnotation( javaClass, be.idamediafoundry.sofa.livecycle.dsc.annotations.Service.class); if (serviceAnnotation != null) { if (!javaClass.isAbstract() && !javaClass.isInterface() && javaClass.isPublic() && !javaClass.isA("java.lang.Throwable")) { accept = true; } else { throw new RuntimeException( "You should not annotate this class with @Service. Only public non-abstract classes are supported (exceptions excluded)."); } } return accept; } @Override public boolean acceptAsOperation(JavaMethod javaMethod) { Type methodResultType = javaMethod.getReturnType(); FactoryMethod factoryMethod = findAnnotation(javaMethod, FactoryMethod.class); return javaMethod.isPublic() && methodResultType != null && !javaMethod.isPropertyAccessor() && !javaMethod.isPropertyMutator() && factoryMethod == null; } @Override public boolean acceptAsConfigParameter(JavaMethod javaMethod) { return javaMethod.isPropertyMutator(); } /** * Find an annotation of a given type on the given entity. * * @param entity The entity on which we are looking for the annotation * @param type the type of the annotation * @return the annotation of the given type on the given entity, or null if * none is found. */ private <T extends java.lang.annotation.Annotation> T findAnnotation( AbstractJavaEntity entity, Class<T> type) { Annotation[] annotations = entity.getAnnotations(); T result = null; if (annotations != null) { for (Annotation annotation : annotations) { if (annotation.getType().getFullyQualifiedName() .equals(type.getName())) { result = convertToJavaLang(annotation, type); break; } } } return result; } private JavaMethod findAnnotationOnMethods(JavaClass javaClass, Class<? extends java.lang.annotation.Annotation> annotationClass) { JavaMethod result = null; JavaMethod[] methods = javaClass.getMethods(); for (JavaMethod javaMethod : methods) { java.lang.annotation.Annotation factoryMethod = findAnnotation( javaMethod, annotationClass); if (factoryMethod != null) { result = javaMethod; break; } } return result; } private <T extends java.lang.annotation.Annotation> T convertToJavaLang( final Annotation annotation, final Class<T> expectedType) { try { final Class<?> annotationClass = Class.forName(annotation.getType() .getFullyQualifiedName()); @SuppressWarnings("unchecked") T proxy = (T) Proxy.newProxyInstance(this.getClass() .getClassLoader(), new Class[]{annotationClass}, new InvocationHandler() { public Object invoke(Object instance, Method method, Object[] args) throws Throwable { if (method.getName().equals("toString")) { return "Proxied annotation of type " + annotationClass; } else if (method.getName().equals("getClass")) { return annotationClass; } Object value = annotation.getProperty(method .getName()); if (value == null) { return method.getDefaultValue(); } if (value instanceof Annotation) { java.lang.annotation.Annotation sub = convertToJavaLang( (Annotation) value, java.lang.annotation.Annotation.class); return sub; } else { AnnotationConstant constant = (AnnotationConstant) value; value = constant.getValue(); return value; } } }); return proxy; } catch (ClassNotFoundException e) { throw new IllegalArgumentException( "The source code is annotated with a class that could not be found on your project's classpath, please fix this!", e); } } }